#include <ctype.h>
#include <limits.h>
#include <float.h>
#include <math.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define _UNICODE
#define UNICODE
#define WINVER 0x0500
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#ifdef _MSC_VER
	#pragma comment(lib, "user32")
	#pragma comment(lib, "gdi32")
#endif

#define code
#define xdata
typedef const unsigned char type_cbyte;
typedef bool bit;

#include "hcgrom.c"
#include "cgrom.c"

void on_init(void);
void (*on_keydown)(uint8_t key) = NULL;

#define szTitle LCD_TITLE
#define szWindowClass L"LCD_View"
#define dwWindowStyle (WS_TILED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX)
HINSTANCE hInst;
HWND hWnd = NULL;
HFONT ui_font;

#ifndef LCD_TITLE
	#define LCD_TITLE L""
#endif
#ifndef LCD_WIDTH
	#define LCD_WIDTH 128
#endif
#ifndef LCD_HEIGHT
	#define LCD_HEIGHT 64
#endif
#ifndef LCD_BUTTON_ROWS
	#define LCD_BUTTON_ROWS 8
#endif
#ifndef LCD_BUTTON_COLUMNS
	#define LCD_BUTTON_COLUMNS 8
#endif
#ifndef LCD_BUTTON_WIDTH
	#define LCD_BUTTON_WIDTH 32
#endif
#ifndef LCD_BUTTON_HEIGHT
	#define LCD_BUTTON_HEIGHT 32
#endif
#ifndef LCD_TEXT_LINES
	#define LCD_TEXT_LINES 5
#endif
#ifndef LCD_TEXT_COLUMNS
	#define LCD_TEXT_COLUMN 16
#endif
bit led_inv;
bit lcd_inv;
bit gdram[LCD_WIDTH][LCD_HEIGHT];
uint8_t address_x, address_y;
char ddram[LCD_TEXT_LINES][LCD_TEXT_COLUMNS];
bit lcd_visible;
bit lcd_cursor;
bit lcd_cursor_blink;
uint8_t lcd_cursor_line, lcd_cursor_column;
uint8_t lcd_scroll_top;
bit cgram[4][16][16];
HWND hWnd_buttons[LCD_BUTTON_ROWS * LCD_BUTTON_COLUMNS];
int zoom;
int button_x, button_y, lcd_x, lcd_y;
RECT lcd_edge;
int brush_set;
HBRUSH brush_on, brush_off;

void draw_pixel(HDC hdc, int x, int y) {
	RECT rect;
	bool color;
	int line, column;
	char ch1, ch2, glyph_scanline;
	rect.left = lcd_x + x * zoom;
	rect.top = lcd_y + y * zoom;
	rect.right = rect.left + zoom;
	rect.bottom = rect.top + zoom;
	line = x / 8;
	column = y / 16;
	if (line < LCD_TEXT_LINES && column < LCD_TEXT_COLUMNS) {
		ch1 = ddram[line][column];
		if (ch1 & 0xf0) {
			if (column & 1) {
				ch2 = ch1;
				ch1 = ddram[line][column - 1];
			} else {
				ch2 = ddram[line][column + 1];
			}
			glyph_scanline = cgrom
				[ch1 - 0xa1]
				[ch2 - 0xa1]
				[((y & 15) << 1) | (column & 1)];
		} else {
			glyph_scanline = hcgrom[ch1];
		}
		color = (glyph_scanline >> (x & 7)) & 1;
	} else {
		color = 0;
	}
	color ^= gdram[x][y];
	FillRect(hdc, &rect, color ? brush_on : brush_off);
}

void lcd_clear_memory(void) {
	memset(gdram, 0, sizeof(gdram));
	memset(ddram, ' ', sizeof(ddram));
}

void lcd_clear(void) {
	HDC hdc;
	RECT rect;
	lcd_clear_memory();
	hdc = GetDC(hWnd);
	rect.left = lcd_x;
	rect.top = lcd_y;
	rect.right = rect.left + zoom * LCD_WIDTH;
	rect.bottom = rect.top + zoom * LCD_HEIGHT;
	FillRect(hdc, &rect, brush_off);
	ReleaseDC(hWnd, hdc);
}

void lcd_set_address(uint8_t x, uint8_t y) {
	if (x >= 0 && x < LCD_WIDTH) address_x = x;
	if (y >= 0 && y < LCD_HEIGHT) address_y = y;
}

void inc_address() {
	address_x++;
	if (address_x > 7) {
		address_x = 0;
		address_y ^= 32;
	}
}

uint16_t lcd_read_word(void) {
	uint8_t i;
	uint16_t r = 0;
	for (i = 0; i < 16; i++) {
		r |= (uint16_t) gdram[(address_x << 4) | i][address_y] << i;
	}
	inc_address();
	return r;
}

void lcd_write_word(uint16_t word) {
	uint8_t i;
	for (i = 0; i < 16; i++) {
		gdram[(address_x << 4) | i][address_y] = (word >> i) & 1;
	}
	inc_address();
}

void lcd_set_text(char* text) {
	uint8_t i, j;
	for (i = 0; i < LCD_TEXT_LINES; i++) {
		for (j = 0; j < LCD_TEXT_COLUMNS; j++) {
			ddram[i][j] = *text;
			text++;
		}
	}
}

void lcd_set_visible(bit visible) {
	lcd_visible = visible;
}

void lcd_set_cursor(uint8_t line, uint8_t column, bit blink) {
	lcd_cursor = true;
	lcd_cursor_line = line;
	lcd_cursor_column = column;
	lcd_cursor_blink = blink;
}

void lcd_hide_cursor(void) {
	lcd_cursor = false;
}

void lcd_set_scroll_top(uint8_t y) {
	lcd_scroll_top = y;
}

void get_window_size_correct(void) {
	if (hWnd) {
		int x, y, edge;
		RECT rect;
		edge = GetSystemMetrics(SM_CXEDGE);
		rect.left = 0;
		rect.top = 0;
		x = LCD_BUTTON_COLUMNS * LCD_BUTTON_WIDTH;
		y = LCD_WIDTH * zoom + edge * 2;
		if (x > y) {
			rect.right = x;
			button_x = 0;
			lcd_x = (x - y) / 2 + edge;
		} else {
			rect.right = y;
			button_x = (y - x) / 2;
			lcd_x = edge;
		}
		lcd_y = GetSystemMetrics(SM_CYEDGE);
		button_y = lcd_y * 2 + LCD_HEIGHT * zoom;
		rect.bottom = button_y + LCD_BUTTON_ROWS * LCD_BUTTON_HEIGHT;
		lcd_edge.left = lcd_x - edge;
		lcd_edge.top = 0;
		lcd_edge.right = lcd_edge.left + y;
		lcd_edge.bottom = button_y;
		AdjustWindowRect(&rect, dwWindowStyle, 0);
		SetWindowPos(hWnd, NULL, -1, -1, rect.right - rect.left, rect.bottom - rect.top, SWP_NOZORDER | SWP_NOMOVE);
		for (y = 0; y < LCD_BUTTON_ROWS; y++) {
			for (x = 0; x < LCD_BUTTON_COLUMNS; x++) {
				MoveWindow(hWnd_buttons[y * LCD_BUTTON_COLUMNS + x], button_x + x * LCD_BUTTON_WIDTH, button_y + y * LCD_BUTTON_HEIGHT, LCD_BUTTON_WIDTH, LCD_BUTTON_HEIGHT, true);
			}
		}
	}
}

void update_zoom(int new_zoom) {
	if (zoom == new_zoom) return;
	zoom = new_zoom;
	get_window_size_correct();
	UpdateWindow(hWnd);
}

void lcd_clear_button_text() {
	int i;
	for (i = 0; i < LCD_BUTTON_ROWS * LCD_BUTTON_COLUMNS; i++) {
		SetWindowText(hWnd_buttons[i], L"");
	}
}

void lcd_set_button_text(int index, wchar_t* text) {
	SetWindowText(hWnd_buttons[index], text);
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
	switch (message) {
	case WM_CREATE:
		lcd_clear_memory();
		zoom = 2;
		break;
	case WM_SHOWWINDOW:
		if (wParam && !lParam) {
			get_window_size_correct();
		}
		break;
	case WM_COMMAND:
		if (lParam) {
			if (HIWORD(wParam) == BN_CLICKED) {
				if (on_keydown) on_keydown(LOWORD(wParam));
			}
		} else {
			switch (wParam) {
			case 1:
				break;
			default:
				MessageBox(hWnd, L"Unknown menu identifier.", szTitle, MB_ICONSTOP);
			}
		}
		break;
	case WM_PAINT:
		{
			PAINTSTRUCT ps;
			HDC hdc = BeginPaint(hWnd, &ps);
			int x, y;
			SetBkMode(hdc, TRANSPARENT);
			DrawEdge(hdc, &lcd_edge, EDGE_SUNKEN, BF_RECT);
			for (x = 0; x < LCD_WIDTH; x++) {
				for (y = 0; y < LCD_HEIGHT; y++) {
					draw_pixel(hdc, x, y);
				}
			}
			EndPaint(hWnd, &ps);
		}
		break;
	case WM_KEYDOWN:
		switch (wParam) {
		case VK_F1:
			update_zoom(1);
			break;
		case VK_F2:
			update_zoom(2);
			break;
		case VK_F3:
			update_zoom(3);
			break;
		case VK_F4:
			update_zoom(4);
			break;
		default:
			on_keydown(wParam);
		}
		break;
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
	int x, y;
	WNDCLASSEXW wcex;

	wcex.cbSize = sizeof(WNDCLASSEX);

	wcex.style          = CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc    = WndProc;
	wcex.cbClsExtra     = 0;
	wcex.cbWndExtra     = 0;
	wcex.hInstance      = hInstance;
	wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(1));
	wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground  = (HBRUSH) (COLOR_3DFACE + 1);
	wcex.lpszMenuName   = NULL;
	wcex.lpszClassName  = szWindowClass;
	wcex.hIconSm        = NULL;

	RegisterClassExW(&wcex);

	hInst = hInstance;

	brush_set = 2;
	switch (brush_set) {
	case 1:
	default:
		brush_on = GetSysColorBrush(COLOR_WINDOWTEXT);
		brush_off = GetSysColorBrush(COLOR_WINDOW);
		break;
	case 2:
		brush_on = CreateSolidBrush(RGB(0, 0, 128));
		brush_off = CreateSolidBrush(RGB(255, 255, 255));
		break;
	case 3:
		brush_on = GetStockObject(BLACK_BRUSH);
		brush_off = GetStockObject(WHITE_BRUSH);
		break;
	case 4:
		brush_on = GetStockObject(WHITE_BRUSH);
		brush_off = GetStockObject(BLACK_BRUSH);
		break;
	}
	ui_font = GetStockObject(DEFAULT_GUI_FONT);

	hWnd = CreateWindowW(szWindowClass, szTitle, dwWindowStyle, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
	if (!hWnd) return FALSE;

	for (y = 0; y < LCD_BUTTON_ROWS; y++) {
		for (x = 0; x < LCD_BUTTON_COLUMNS; x++) {
			int i = y * LCD_BUTTON_COLUMNS + x;
			hWnd_buttons[i] = CreateWindowW(
				L"Button", L"",
				WS_CHILD | WS_VISIBLE | WS_TABSTOP,
				0, 0, LCD_BUTTON_WIDTH, LCD_BUTTON_HEIGHT,
				hWnd, (HMENU) i, hInstance, NULL
			);
			SendMessage(hWnd_buttons[i], WM_SETFONT, (WPARAM) ui_font, false);
		}
	}

	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);
	on_init();

	MSG msg;

	while (GetMessage(&msg, NULL, 0, 0)) {
		if (!IsDialogMessage(hWnd, &msg)) {
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	return (int) msg.wParam;
}
